#include "AudioOutPool.h"
#include <list>
#include <FMedia.h>
#include "Gluck.h"

using namespace Osp::Media;

#ifdef _DEBUG
	#define AUDIO_OUT_POOL_DEBUG_LOGGING
#endif

#ifdef AUDIO_OUT_POOL_DEBUG_LOGGING
	#define AOP_LOG_DEBUG(...) AppLog(__VA_ARGS__)
#else
	#define AOP_LOG_DEBUG(...) ((void) 0)
#endif

namespace liba {
namespace noise {
namespace hard {
namespace samsung {

class AudioOutEventListener
	: public IAudioOutEventListener
{
public:
	AudioOutEventListener()
		: events(0)
	{
	}

	virtual void OnAudioOutBufferEndReached(Osp::Media::AudioOut& src)
	{
		AppLog("AudioOutBufferEndReached");
		if (events)
			events->AudioOutEndOfBuffer();
	}

	virtual void OnAudioOutInterrupted(Osp::Media::AudioOut& src)
	{
		AppLog("AudioOutInterrupted");
	}

	virtual void OnAudioOutReleased(Osp::Media::AudioOut& src)
	{
		AppLog("AudioOutReleased");
	}

	void SetEvents(AudioOutEvents* events)
	{
		this->events = events;
	}
private:
	AudioOutEvents* events;
};

struct PoolElement
{
	PoolElement(AudioOut* audioOut, AudioOutEventListener* listener, bool stereo, bool reserved)
		: audioOut(audioOut)
		, listener(listener)
		, stereo(stereo)
		, reserved(reserved)
	{
	}

	AudioOut* audioOut;
	AudioOutEventListener* listener;

	bool stereo;
	bool reserved;
};

const int POOL_OUT_FREQUENCY = 22050;
const int POOL_STEREO_OUTS = 2;
const int POOL_MONO_OUTS = 4;

PoolElement CreateAudioOut(bool stereo)
{
	AutoPtr<AudioOutEventListener> listener(new AudioOutEventListener());
	AutoPtr<AudioOut> out(new AudioOut());
	result bada_r;

	bada_r = out->Construct(*listener);
	if (E_SUCCESS != bada_r)
		throw Gluck(__FUNCTION__, "Couldn't construct audio out");

	bada_r = out->Prepare(AUDIO_TYPE_PCM_S16_LE,
			stereo ? AUDIO_CHANNEL_TYPE_STEREO : AUDIO_CHANNEL_TYPE_MONO,
			POOL_OUT_FREQUENCY);

	if (E_SUCCESS != bada_r)
		AppLog("Couldn't prepare audio out");

	return PoolElement(out.release(), listener.release(), stereo, false);
}

class AudioOutPoolImpl
{
	typedef std::list<PoolElement> PoolList;
public:
	AudioOutPoolImpl()
	{
	}

	~AudioOutPoolImpl()
	{
		for (PoolList::iterator i = pool.begin(); i != pool.end(); ++i)
		{
			delete i->audioOut;
			delete i->listener;
		}
	}
public:
	void Initialize()
	{
		for (int i = 0; i < POOL_STEREO_OUTS; ++i)
			pool.push_back(CreateAudioOut(true));

		for (int i = 0; i < POOL_MONO_OUTS; ++i)
			pool.push_back(CreateAudioOut(false));
	}

	AudioOutHandle Request(bool stereo, AudioOutPool* pThis)
	{
		if (stereo)
			AOP_LOG_DEBUG("[AudioOutPoolImpl] Requested stereo audio output");
		else
			AOP_LOG_DEBUG("[AudioOutPoolImpl] Requested mono audio output");

		for (PoolList::iterator i = pool.begin(); i != pool.end(); ++i)
		{
			if (i->stereo == stereo && !i->reserved)
			{
				AOP_LOG_DEBUG("[AudioOutPoolImpl] Reserving audio out 0x%X8", &(*i));
				i->reserved = true;
				logStatus();
				return AudioOutHandle(&(*i), pThis);
			}
		}

		// Return null handle indicating that we couldn't find one.
		AOP_LOG_DEBUG("[AudioOutPoolImpl] Rejecting request because no free audio outs available");
		logStatus();
		return AudioOutHandle();
	}

	void Release(AudioOutHandle& handle)
	{
		if (!handle.element)
		{
			AOP_LOG_DEBUG("[AudioOutPoolImpl] We're asked to release NULL audio output. Suppressing.");
			return;
		}

		handle.element->audioOut->Reset();
		handle.element->reserved = false;

		AOP_LOG_DEBUG("[AudioOutPoolImpl] Releasing audio output 0x%X8", handle.element);
		handle.element = 0;
		handle.pool = 0;

		logStatus();
	}

	void logStatus()
	{
#ifdef AUDIO_OUT_POOL_DEBUG_LOGGING
		int allocatedStereo, allocatedMono, freeStereo, freeMono;
		allocatedStereo = allocatedMono = freeStereo = freeMono = 0;

		for (PoolList::iterator i = pool.begin(); i != pool.end(); ++i)
		{
			if (i->stereo)
				if (i->reserved)
					++allocatedStereo;
				else
					++freeStereo;
			else
				if (i->reserved)
					++allocatedMono;
				else
					++freeMono;
		}

		AppLog("[AudioOutPool] Stereo outs: %d reserved, %d free. Mono outs: %d reserved, %d free",
				allocatedStereo, freeStereo, allocatedMono, freeMono);
#endif
	}
private:
	PoolList pool;
};

AudioOutPool::AudioOutPool()
	: impl(new AudioOutPoolImpl())
{ }

AudioOutPool::~AudioOutPool()
{
	delete impl;
}

AudioOutPool* AudioOutPool::GetInstance()
{
	if (!instance)
	{
		instance = new AudioOutPool();
		instance->impl->Initialize();
	}
	return instance;
}

AudioOutHandle AudioOutPool::Request(bool stereo)
{
	return impl->Request(stereo, this);
}

void AudioOutPool::Release(AudioOutHandle& handle)
{
	impl->Release(handle);
}

AutoPtr<AudioOutPool> AudioOutPool::instance;

AudioOutHandle::AudioOutHandle()
	: element(0)
	, pool(0)
{
}

AudioOutHandle::AudioOutHandle(const AudioOutHandle& rhs)
	: element(rhs.element)
	, pool(rhs.pool)
{
	rhs.element = 0;
	rhs.pool = 0;
}

AudioOutHandle::AudioOutHandle(PoolElement* element, AudioOutPool* pool)
	: element(element)
	, pool(pool)
{
}

AudioOutHandle::~AudioOutHandle()
{
	this->release();
}

AudioOutHandle& AudioOutHandle::operator=(const AudioOutHandle& rhs)
{
	this->release();

	element = rhs.element;
	pool = rhs.pool;

	rhs.element = 0;
	rhs.pool = 0;

	return *this;
}

AudioOut* AudioOutHandle::operator->() const
{
	return element->audioOut;
}

AudioOutHandle::operator bool() const
{
	return element != 0;
}

void AudioOutHandle::release()
{
	if (!element || !pool)
		return;

	element->listener->SetEvents(0);
	pool->Release(*this);
}

void AudioOutHandle::Subscribe(AudioOutEvents* subscriber)
{
	if (element)
		element->listener->SetEvents(subscriber);
}

void AudioOutHandle::Unsubscribe()
{
	if (element)
		element->listener->SetEvents(0);
}

} // namespace samsung
} // namespace hard
} // namespace noise
} // namespace liba
